package jhunter.dao;

import java.util.ArrayList;
import java.util.List;

import javax.sql.DataSource;

import jhunter.common.exception.ConfigException;
import jhunter.dao.bo.BaseObject;
import jhunter.dao.condition.Condition;
import jhunter.dao.condition.ExampleCondition;
import jhunter.dao.condition.NullPropertyCondition;
import jhunter.dao.condition.PropertyCondition;
import jhunter.dao.dialect.Dialect;
import jhunter.dao.page.Page;
import jhunter.dao.select.Order;
import jhunter.dao.select.SelectColumn;
import jhunter.dao.type.TypeHandlerFactory;

import org.apache.commons.logging.Log;
import org.apache.commons.logging.LogFactory;
import org.springframework.jdbc.core.JdbcTemplate;

import schema.vo.module.Relationship;

public class DefaultDataAccess implements DataAccess {

	private static Log log = LogFactory.getLog(DefaultDataAccess.class);

	private JdbcTemplate jdbcTemplate;

	private EntityContainer entityContainer;

	private EntityExt entity;

	private Dialect dialect;

	public DefaultDataAccess(EntityContainer entityContainer, String entityId) {
		super();
		this.entityContainer = entityContainer;
		this.entity = entityContainer.getEntityExt(entityId);
		if (entity == null) {
			throw new ConfigException("can not found entityId :" + entityId);
		}
	}

	public void setDataSource(DataSource dataSource) {
		jdbcTemplate = new JdbcTemplate(dataSource);
	}

	protected Object convertToSqlParameter(String propertyName, Object value) {
		return TypeHandlerFactory.getTypeHandler(entity.getPropsMap().get(propertyName).getType()).getSqlObject(value);
	}
	
	public BaseObject findById(Long id) {
		PropertyCondition condition = new PropertyCondition(entity, getEntity().getPrimaryProp(), "=", id);
		List<BaseObject> list = this.query(condition);
		if(list == null || list.size() != 1) {
			throw new RuntimeException("can not found.");
		}
		return list.get(0);
	}

	@Override
	public void insert(BaseObject bo) {
		String pk = entity.getPrimaryProp();
		if(!bo.containsKey(pk)) {
			long id = entityContainer.getUuidShorter().nextVal();
			bo.addValue(pk, id);
		}
		
		List<Object> args = new ArrayList<Object>();
		StringBuilder sb = new StringBuilder("insert into ");
		sb.append(entity.getTableName());
		sb.append("(");
		List<String> props = entity.getProperties();
		for (int i = 0; i < props.size(); i++) {
			String p = props.get(i);
			sb.append(entity.getColumnName(p));
			if (i != props.size() - 1) {
				sb.append(",");
			}
		}
		sb.append(")");
		sb.append("values");
		sb.append("(");
		for (int i = 0; i < props.size(); i++) {
			String p = props.get(i);
			args.add(convertToSqlParameter(p, bo.get(p)));
			sb.append("?");
			if (i != props.size() - 1) {
				sb.append(",");
			}
		}
		sb.append(")");
		log.debug(sb.toString());
		jdbcTemplate.update(sb.toString(), args.toArray(new Object[0]));
	}

	public void delete(Long id) {
		delete(getPropertyCondition(entity.getPrimaryProp(), "=", id));
	}

	public PropertyCondition getPropertyCondition(String pName, String op, Object value) {
		return new PropertyCondition(entity, pName, op, value);
	}
	
	@Override
	public NullPropertyCondition getNullPropertyCondition(String pName) {
		return new NullPropertyCondition(entity, pName);
	}

	public void delete(Condition condition) {
		List<Object> args = new ArrayList<Object>();
		StringBuilder sb = new StringBuilder("delete "+entity.getTableAlias()+" from ");
		sb.append(entity.getTableName());
		sb.append(" as ");
		sb.append(entity.getTableAlias());
		if (!condition.isEmpty()) {
			sb.append(" where ");
			sb.append(condition.getWhereSql());
			args.addAll(condition.getParameters());
		}
		log.debug(sb.toString());
		jdbcTemplate.update(sb.toString(), args.toArray(new Object[0]));
	}

	/**
	 * update table1 set a=1, b=2 where id = ?
	 * 
	 * @param value
	 * @param id
	 */
	public void update(BaseObject value, Long id) {
		update(value, getPropertyCondition(entity.getPrimaryProp(), "=", id));
	}

	public void update(BaseObject value, Condition condition) {
		List<Object> args = new ArrayList<Object>();
		StringBuilder sb = new StringBuilder("update ");
		sb.append(entity.getTableName());
		sb.append(" as ").append(entity.getTableAlias());
		sb.append(" set ");
		for (int i = 0; i < entity.getProperties().size(); i++) {
			String p = entity.getProperties().get(i);
			sb.append(entity.getTableAlias()).append(".");
			sb.append(entity.getColumnName(p)).append("=").append("?");
			args.add(convertToSqlParameter(p, value.get(p)));
			if (i != entity.getProperties().size() - 1) {
				sb.append(",");
			}
		}
		if (!condition.isEmpty()) {
			sb.append(" where ");
			sb.append(condition.getWhereSql());
			args.addAll(condition.getParameters());
		}
		log.debug(sb.toString());
		jdbcTemplate.update(sb.toString(), args.toArray(new Object[0]));
	}

	public void updateIgnoreNull(BaseObject value, Condition condition) {
		List<Object> args = new ArrayList<Object>();
		StringBuilder sb = new StringBuilder("update ");
		sb.append(entity.getTableName());
		sb.append(" as ").append(entity.getTableAlias());
		sb.append(" set ");
		for (int i = 0; i < entity.getProperties().size(); i++) {
			String p = entity.getProperties().get(i);
			if (value.get(p) != null) {
				sb.append(entity.getColumnName(p)).append("=").append("?");
				args.add(convertToSqlParameter(p, value.get(p)));
				sb.append(",");
			}
		}
		if (sb.toString().endsWith(",")) {
			sb.deleteCharAt(sb.toString().length() - 1);
		}
		if (!condition.isEmpty()) {
			sb.append(" where ");
			sb.append(condition.getWhereSql());
			args.addAll(condition.getParameters());
		}

		log.debug(sb.toString());
		jdbcTemplate.update(sb.toString(), args.toArray(new Object[0]));
	}

	public void updateIgnoreNull(BaseObject value, Long id) {
		updateIgnoreNull(value, getPropertyCondition(entity.getPrimaryProp(), "=", id));
	}

	public Page<BaseObject> query(Page<BaseObject> page, Condition condition) {
		List<SelectColumn> columns = new ArrayList<SelectColumn>();
		List<Object> args = new ArrayList<Object>();
		String sql = getQuerySql(page, condition, null, columns, args);
		List<BaseObject> list = jdbcTemplate.query(sql, args.toArray(new Object[0]), new ObjectRowMapper(entity, columns));
		page.setResult(list);
		if (page.isAutoCount()) {
			int totalCount = count(condition);
			page.setTotalCount(totalCount);
		}
		return page;
	}

	public int count(Condition condition) {
		List<Object> args = new ArrayList<Object>();
		StringBuilder sql = new StringBuilder("select count(*) from ");
		sql.append(entity.getTableName()).append(" as ").append(entity.getTableAlias());
		List<String> refs = entity.getRefRelationships();
		for (String r : refs) {
			Relationship rs = entity.getRelationshipsMap().get(r);
			String target = rs.getTargetId();
			EntityExt re = entityContainer.getEntityExt(target);
			sql.append(" left join ").append(re.getTableName()).append(" as ").append(rs.getName());
			sql.append(" on ").append(entity.getTableAlias()).append(".").append(entity.getColumnName(rs.getSourceProperty()));
			sql.append(" = ").append(rs.getName()).append(".").append(re.getColumnName(rs.getTargetProperty()));
		}
		if (condition != null && !condition.isEmpty()) {
			sql.append(" where ");
			sql.append(condition.getWhereSql());
			args.addAll(condition.getParameters());
		}
		Integer count = jdbcTemplate.queryForObject(sql.toString(), Integer.class);
		return count == null ? 0 : count;
	}

	public String getQuerySql(Page<BaseObject> page, Condition condition, Order[] ords, List<SelectColumn> columns, List<Object> args) {
		Order[] orders = null;
		if (page == null) {
			orders = ords;
		} else {
			orders = page.getOrders().toArray(new Order[0]);
		}
		long start = System.currentTimeMillis();
		StringBuilder sql = new StringBuilder("select ");
		int ii = 0;
		for (int i = 0; i < entity.getProperties().size(); i++) {
			String p = entity.getProperties().get(i);
			String column = entity.getColumnName(p);
			String columnAlias = column + "_" + (ii++);
			sql.append(entity.getTableAlias()).append(".").append(column).append(" as ").append(columnAlias);

			columns.add(new SelectColumn(p, columnAlias, entity.getPropsMap().get(p).getType(), null));

			if (i != entity.getProperties().size() - 1) {
				sql.append(",");
			}
		}
		List<String> refs = entity.getRefRelationships();
		if (refs.size() > 0) {
			sql.append(",");
		}
		for (String r : refs) {
			Relationship rs = entity.getRelationshipsMap().get(r);
			String target = rs.getTargetId();
			EntityExt re = entityContainer.getEntityExt(target);
			for (int i = 0; i < re.getProperties().size(); i++) {
				String p = re.getProperties().get(i);
				String column = re.getColumnName(p);
				String columnAlias = column + "_" + (ii++);
				sql.append(r).append(".").append(column).append(" as ").append(columnAlias);

				columns.add(new SelectColumn(p, columnAlias, re.getPropsMap().get(p).getType(), r));

				sql.append(",");
			}
		}
		if (sql.toString().endsWith(",")) {
			sql.deleteCharAt(sql.toString().length() - 1);
		}

		sql.append(" from ");
		sql.append(entity.getTableName()).append(" as ").append(entity.getTableAlias());
		for (String r : refs) {
			Relationship rs = entity.getRelationshipsMap().get(r);
			String target = rs.getTargetId();
			EntityExt re = entityContainer.getEntityExt(target);
			sql.append(" left join ").append(re.getTableName()).append(" as ").append(rs.getName());
			sql.append(" on ").append(entity.getTableAlias()).append(".").append(entity.getColumnName(rs.getSourceProperty()));
			sql.append(" = ").append(rs.getName()).append(".").append(re.getColumnName(rs.getTargetProperty()));
		}
		if (condition != null && !condition.isEmpty()) {
			sql.append(" where ");
			sql.append(condition.getWhereSql());
			args.addAll(condition.getParameters());
		}
		if (orders != null) {
			if (orders.length > 0) {
				sql.append(" order by ");
			}
			for (int i = 0; i < orders.length; i++) {
				Order order = orders[i];
				String alias = entity.getTableAlias();
				if (order.getRefName() != null && entity.getRelationshipsMap().containsKey(order.getRefName())) {
					alias = order.getRefName();
				}
				String orderStr = alias + "." + order.getPropertyName();
				if (order.isIgnoreCase()) {
					orderStr = "upper(" + orderStr + ")";
				}
				sql.append(orderStr);
				sql.append(order.isAscending() ? " asc " : " desc ");
				if (i != orders.length - 1) {
					sql.append(",");
				}
			}
		}
		long end = System.currentTimeMillis();
		String querySql = sql.toString();
		log.debug(sql.toString());
		log.debug("拼装SQL耗时:" + (end - start) + "ms");
		if (page == null) {
			return querySql;
		}
		querySql = dialect.page(page, querySql, args);
		return querySql;
	}

	public List<BaseObject> query(Condition condition, Order... orders) {
		List<SelectColumn> columns = new ArrayList<SelectColumn>();
		List<Object> args = new ArrayList<Object>();
		String sql = getQuerySql(null, condition, orders, columns, args);
		List<BaseObject> list = jdbcTemplate.query(sql, args.toArray(new Object[0]), new ObjectRowMapper(entity, columns));
		return list;
	}

	public Dialect getDialect() {
		return dialect;
	}

	public void setDialect(Dialect dialect) {
		this.dialect = dialect;
	}

	@Override
	public ExampleCondition createExample(BaseObject example) {
		return new ExampleCondition(entity, example);
	}
	
	public ExampleCondition createOrExample(BaseObject example) {
		return new ExampleCondition(entity, example,"or");
	}

	public EntityExt getEntity() {
		return entity;
	}

}
